#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "thread_list.h"
#include "user_mutex.h"
#include "user_threads.h"
#include "common.h"

struct umutex_struct {
        uthread_id owner_uthread_id; //ID of holding thread. -1 if unlocked
        thread_list *wait_queue; //lock queue
        unsigned int lock_count; //enforce recursive lock mechanism
};

thread_list *mutex_list;

int is_mutex_locked(uthread_mutex_t m) {
	int retVal =  m->owner_uthread_id >= 0;
	if (retVal < 0) {
		COND_ASSERT(m->lock_count == 0);
	}
	return retVal;
}

int is_mutex_initialized(uthread_mutex_t m) {
	return thread_list_is_in(mutex_list,m);
}

int is_mutex_queue_empty(uthread_mutex_t m) {
	return thread_list_is_empty(m->wait_queue);
}

/*initializes mutex_list*/
int uthread_mutex_init_general() {
	mutex_list = thread_list_create();
	if (!mutex_list) {
		return MUTEX_FAILURE;
	}
	return MUTEX_SUCCESS;
}

/*frees all mutexes in mutex_list and destroys mutex_list*/
void uthread_mutex_free_general()
{
	node *n;
	//first free mutexes
	thread_list_for_each(mutex_list,n) {
		uthread_mutex_t mutex = n->entry;
		thread_list_destroy(mutex->wait_queue);
		free(mutex);
	}
	//destroy list of mutexes
	thread_list_destroy(mutex_list);
}

int uthread_mutex_init(uthread_mutex_t* mutex) {
	lock_alarm();
	//validate input
	if(!mutex){
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	//allocate and reset mutex
	*mutex = (uthread_mutex_t)malloc(sizeof(struct umutex_struct));
	if (!(*mutex)){
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	(*mutex)->owner_uthread_id = -1;
	(*mutex)->wait_queue = thread_list_create();
	if (!((*mutex)->wait_queue)) {
		free (*mutex);
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	(*mutex)->lock_count = 0;
	//insert mutex to mutex_list
	if (thread_list_insert_head(mutex_list, (void*)*mutex) != HW3_SUCCESS){
		thread_list_destroy((*mutex)->wait_queue);
		free(*mutex);
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_destroy(uthread_mutex_t mutex) {
	lock_alarm();
	//validate input
	if (!mutex){
		unlock_alarm();
		return MUTEX_INVALID;
	}
	//validate that mutex is initialized
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	//validate that mutex is unlocked
	if (is_mutex_locked(mutex)) {
		unlock_alarm();
		return MUTEX_LOCKED;
	}

	COND_ASSERT(thread_list_is_empty(mutex->wait_queue));
	//destroy wait queue
	if (thread_list_destroy(mutex->wait_queue) != HW3_SUCCESS) {
		COND_ASSERT(0);
	}
	//remove from mutex list
	if (!thread_list_remove(mutex_list, mutex)) {
		COND_ASSERT(0);
	}
	free(mutex);
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_lock(uthread_mutex_t mutex) {
	lock_alarm();
	//validate input
	if (!mutex) {
		unlock_alarm();
		return MUTEX_INVALID;
	}
	//validate that mutex is initialized
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	if (!is_mutex_locked(mutex)) {  //mutex is unlocked
		COND_ASSERT(mutex->lock_count == 0);
		COND_ASSERT(thread_list_is_empty(mutex->wait_queue));
		mutex->owner_uthread_id = uthread_self(); //take ownership of the mutex
        if(thread_list_insert_head(current->locked_mutexes, mutex) != HW3_SUCCESS){ //insert mutex to the thread's list of locked mutexes
                unlock_alarm();
                return MUTEX_FAILURE;
        }
        mutex->lock_count = 1; //increment lock count
		unlock_alarm();
		return MUTEX_SUCCESS;
	}
	if (mutex->owner_uthread_id == current->id){ //the mutex is already ours. just increment the lock count
        mutex->lock_count++;
		unlock_alarm();
		return MUTEX_SUCCESS;
	}
	// if we are here then the mutex is already locked by another thread. We'll have to wait for it
	if (thread_list_insert_tail(mutex->wait_queue, current) != HW3_SUCCESS) { //enqueue calling thread in the mutex's wait queue
		unlock_alarm();
		return MUTEX_FAILURE;
	}
	current->mutex_waiting_on = mutex; //hold your horses
	deactivate_thread_and_cs(current);
	//and we're back
	COND_ASSERT(current->mutex_waiting_on == NULL);
	COND_ASSERT(mutex->owner_uthread_id == current->id);
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_try_lock(uthread_mutex_t mutex) {
	lock_alarm();
	//validate input
	if (!mutex) {
		unlock_alarm();
		return MUTEX_INVALID;
	}
	//validate that mutex is initialized
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	if (mutex->owner_uthread_id == uthread_self()){ //the mutex is already ours. just increment the lock count
		COND_ASSERT(is_mutex_locked(mutex));
		COND_ASSERT(mutex->lock_count > 0);
		mutex->lock_count++;
		unlock_alarm();
		return MUTEX_SUCCESS;
	}

	if (is_mutex_locked(mutex)) { //the mutex is already locked by another thread. Come back some other time
		unlock_alarm();
		return MUTEX_LOCKED;
	}

	//if we are here then the mutex is unlocked
	COND_ASSERT(thread_list_is_empty(mutex->wait_queue));
	mutex->owner_uthread_id = uthread_self(); //take ownership of the mutex
    if(thread_list_insert_head(current->locked_mutexes, mutex) != HW3_SUCCESS){  //insert mutex to the thread's list of locked mutexes
            unlock_alarm();
            return MUTEX_FAILURE;
    }
    COND_ASSERT(mutex->lock_count == 0);
    mutex->lock_count = 1; //increment lock count
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_mutex_unlock(uthread_mutex_t mutex) {
	lock_alarm();
	//validate input
	if (!mutex) {
		unlock_alarm();
		return MUTEX_INVALID;
	}	
	//validate that mutex is initialized
	if (!is_mutex_initialized(mutex)){
		unlock_alarm();
		return MUTEX_UNINITIALIZED;
	}
	//Validate that the mutex is locked
	if (!is_mutex_locked(mutex)){
		unlock_alarm();
		return MUTEX_UNLOCKED; //TODO: get mail from TA - is the return value correct?
	}
	//Validate that we own this mutex
	if (mutex->owner_uthread_id != uthread_self()){
		unlock_alarm();
		return MUTEX_LOCKED;
	}
	thread_list* locked_mutexes = current->locked_mutexes;
    COND_ASSERT (thread_list_is_in(locked_mutexes,mutex));

    mutex->lock_count--; //we're jumping the gun with this decrement, but we'll fix it later if we need to
    if (mutex->lock_count == 0) {
		if(!thread_list_remove(locked_mutexes, mutex)){  //remove mutex from the thread's list of locked mutexes
			COND_ASSERT(0);
		}
		if (thread_list_is_empty(mutex->wait_queue)){ //no one is waiting on this mutex. We can reset it's owner flag and return
			mutex->owner_uthread_id = -1;
			unlock_alarm();
			return MUTEX_SUCCESS;
		}
		//someone wants this mutex, let's be nice and give it to him
		mutex->lock_count = 1; //re-increment the lock count
		uthread new_owner;
		new_owner = (uthread) thread_list_remove_head(mutex->wait_queue); //dequeue
		if (!new_owner){
			COND_ASSERT(0);
		}

		if(thread_list_insert_tail(new_owner->locked_mutexes, mutex) != HW3_SUCCESS){ //insert mutex to the new owner's list of locked mutexes
			unlock_alarm();
			return MUTEX_FAILURE;
		}
		mutex->owner_uthread_id = new_owner->id;
		new_owner->mutex_waiting_on = NULL; //new owner is making a comeback
		activate_thread(new_owner);
    }
    else {
    	/* if here then the recursive mutex is still locked */
    }
	unlock_alarm();
	return MUTEX_SUCCESS;
}

int uthread_free_mutexes(uthread thread) {
	//assuming that all signals are blocked prior to calling this function
	COND_ASSERT (thread);
	node* n;
	uthread_mutex_t mutex;
	COND_ASSERT(thread->locked_mutexes);
	thread_list_for_each(thread->locked_mutexes, n) { //iterate over locked mutexes of thread and unlock them
		mutex = (uthread_mutex_t) n->entry;
		COND_ASSERT(mutex->owner_uthread_id == thread->id);
		if (thread_list_is_empty(mutex->wait_queue)){ //no one is waiting on this mutex. We can reset it and return
			mutex->owner_uthread_id = -1;
			mutex->lock_count = 0;
		}
		else { //someone wants this mutex, let's be nice and give it to him
			uthread new_owner = thread_list_remove_head(mutex->wait_queue); //dequeue
			if (!new_owner){
				COND_ASSERT(0);
			}
			if(thread_list_insert_head(new_owner->locked_mutexes, mutex) != HW3_SUCCESS){ //insert mutex to the new owner's list of locked mutexes
				return MUTEX_FAILURE;
			}
			new_owner->mutex_waiting_on = NULL; //new owner is making a comeback
			mutex->owner_uthread_id = new_owner->id;
			mutex->lock_count = 1;
			activate_thread(new_owner);
		}
	}

    if (thread->mutex_waiting_on) { //thread was waiting on something, but since it's biting the dust, let's remove it from the wait queue
    	COND_ASSERT(!thread_list_is_empty(thread->mutex_waiting_on->wait_queue));
    	if (!thread_list_remove(thread->mutex_waiting_on->wait_queue,thread)) {
    		COND_ASSERT(0);
    	}
    	thread->mutex_waiting_on = NULL;
    }
    return MUTEX_SUCCESS;
}
